using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.ComponentModel;
using System.Globalization;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
	/// <summary>
	/// Generic collection class for storing collections of entities of the same type.
	/// </summary>
	/// <remarks>In .NET 2.0 or newer, instead of this class and generated collections, consider using the
	/// generic <b>EntityCollection&lt;TEntity, TTable&gt;</b> implementation. It makes it easier to define 
	/// new strong-typed collections which contain objects derived from generated entity classes.</remarks>
	[Serializable]
    public class EntityCollection : Collection<IEntity>, IEntityCollection
	{   
		#region Private members.

		/// <summary>
		/// Alias of the table/view to which this entity belongs to.
		/// Required as distinct member when deserializing object to recreate member 'table'.
		/// </summary>
		private string tableAlias;

		/// <summary>
		/// Class that holds meta data for the table/view this entity belongs to.
		/// Required as distinct member when deserializing object to recreate member 'table'.
		/// </summary>
		private Type dbTableClass;

		/// <summary>
		/// Holds meta data for the table/view this entity belongs to.
		/// Not serialized because it is very big. This member should never be acessed directly.
		/// Use _Table property as it will recreate this member if it's null (eg. after deserialization).
		/// </summary>
		[NonSerialized]
		private IDbTable table;

		#endregion

		#region Protected members and properties.

		/// <summary>
		/// Base type of entities stored in the EntityCollection.
		/// </summary>
        protected Type EntityType { get; set; }

		/// <summary>
		/// IDbTable class for the entity's table/view.
		/// </summary>
		protected IDbTable _Table
		{
			get
			{
				// Member 'table' may be null after deserialization. It has to be recreated.
				if (this.table == null)
                    this.table = DbTableFactory.CreateTable(this.dbTableClass, this.tableAlias);
				
				return this.table;
			}
			set
			{
				this.table = value;

				// Set info required by Activator.
				this.tableAlias = (value.Alias != value.TableName) ? value.Alias : this.tableAlias = null;
				this.dbTableClass = value.GetType();
			}
		}

        /// <summary>Gets direct access to inner list. Ensures source-code compatibility with previously generated code.</summary>
        protected IList InnerList
        {
            get { return (IList)this.Items; }
        }

		#endregion

		#region Constructors.

        /// <summary>
        /// Initializes a new instance of the EntityCollection class.
        /// </summary>
        /// <remarks>Note to inheritors: <see cref="_Table"/> and <see cref="EntityType"/> properties must be initialized in constructor.</remarks>
        protected EntityCollection()
        { 
        }

		/// <summary>
		/// Initializes a new instance of the EntityCollection class.
		/// </summary>
		/// <param name="dbTable">Contains metadata about entities stored in the EntityCollection.</param>
		/// <remarks>Creates a new EntityCollection for storing entities compatibile with the given IDbTable.</remarks>
		public EntityCollection(IDbTable dbTable)
		{
            _Table = dbTable;
            this.EntityType = dbTable.NewEntity().GetType();	
		}

        /// <summary>
        /// Initializes a new instance of the EntityCollection class.
        /// </summary>
        /// <param name="dbTable">Contains metadata about entities stored in the EntityCollection.</param>
        /// <param name="list">The list that is wrapped by the new collection.</param>
        protected EntityCollection(IDbTable dbTable, IList<IEntity> list)
            : base(list)
        {
            _Table = dbTable;
            this.EntityType = dbTable.NewEntity().GetType();
        }

		#endregion

        #region Properties.

        /// <summary>Gets IDbTable object used by this EntityCollection.</summary>
        public IDbTable IDbTable
        {
            get { return _Table; }
        }

        #endregion

        #region ValidateEntityType.

        /// <summary>
		/// Validates whether the provided entity is compatibile with the current collection.
		/// </summary>
		/// <param name="entity">Entity to validate.</param>
		/// <exception cref="ArgumentException">Generated if entity is not compatibile with the current collection.</exception>
		protected virtual void ValidateEntityType(IEntity entity)
		{
			if (entity == null)
                throw new ArgumentNullException("entity", Messages.EntityCollection_NullsNotAllowed);

			// Given entity must be of the type this.EntityType or inherited.
			if (this.EntityType != null)
			{
				Type objectType = typeof(object);				
				bool isEqualOrInherited = false;

				Type t = entity.GetType();
				while (t != objectType)
				{
					if (t == this.EntityType)
					{
						isEqualOrInherited = true;
						break;
					}

					t = t.BaseType;
				}

                // throw new ArgumentException("Only " + this.EntityType.ToString() + " or derived objects are allowed in the collection.", "entity");
				if (!isEqualOrInherited)
                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Messages.EntityCollection_OnlyXOrDerivedClassesAreAllowed, this.EntityType.ToString()), "entity");
			}			
		}

		#endregion

        #region Overrided Collection<T> SetItem and InsertItem and methods to enforce valid entity types.

        /// <summary>Validates and replaces the element at the specified index.</summary>
        protected override void SetItem(int index, IEntity item)
        {
            ValidateEntityType(item);
            base.SetItem(index, item);
        }

        /// <summary>Validates and inserts an element into the collection at the specified index.</summary>
        protected override void InsertItem(int index, IEntity item)
        {
            ValidateEntityType(item);	
            base.InsertItem(index, item);
        }

        #endregion

		#region Add.

		/// <summary>
		/// Adds an entity to the end of the collection.
		/// </summary>
		/// <param name="entity">The entity to be added to the end of the EntityCollection. 
		/// Nulls are not allowed, otherwise a NoNullAllowedException is generated.
		/// Entity must be an instance of a class compatible with <see cref="IDbTable"/>.</param>
		/// <param name="validateEntityType">Indicates whether the type of entity is validated to
		/// determine if it is an instance of a class compatible with <see cref="IDbTable"/>.
		/// Set to <b>false</b> only when absoulutely sure that the entity is compatibile.
		/// Setting this parameter to <b>false</b> may increase performance.</param>
		public virtual void Add(IEntity entity, bool validateEntityType)
		{
			if (validateEntityType)
				ValidateEntityType(entity);

            // Add directly to underlying collection skipping InsertItem method which forces validation.
			this.Items.Add(entity);
		}

        #endregion

        #region ToArray.

        /// <summary>
        /// Copies the elements of the EntityCollection to a new IEntity array.
        /// </summary>
        /// <returns>An IEntity array containing copies of the elements of the EntityCollection.</returns>
        public virtual IEntity[] ToArray()
        {
            IEntity[] array = new IEntity[this.Count];
            for (int entityIdx = 0; entityIdx < array.Length; entityIdx++)
                array[entityIdx] = this[entityIdx];

            return array;
        }

		#endregion
    }	
}
